package a02;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;

import util.Pair;

public class Go extends AbstractRegularGame<GoMove> implements Serializable {

    private static final long serialVersionUID = 1L;
    private int passed = 0;
    private static int  [] startMovesX = {4,2,6,2,6};
	    private static int  [] startMovesY = {4,2,2,6,6} ;

    public Go() {
	super((byte) 9, (byte) 9);
    }

    @Override
    public Go clone() {
	return (Go) super.clone();
    }

    private boolean coordsValid(int x, int y) {
	boolean valid = true;
	if (x > this.getColumns() - 1) {
	    valid = false;
	}
	if (y > this.getRows() - 1) {
	    valid = false;
	}
	if (x < 0) {
	    valid = false;
	}
	if (y < 0) {
	    valid = false;
	}
	return valid;
    }

    private void dealWithEnemies(Pair<Byte, Byte> m, byte player) {
	final List<List<Pair<Byte, Byte>>> enemyGroups = new LinkedList<List<Pair<Byte, Byte>>>();
	final List<Pair<Byte, Byte>> enemiesDestroyed = new LinkedList<Pair<Byte, Byte>>();
	List<Pair<Byte, Byte>> enemyNeighbours = new LinkedList<Pair<Byte, Byte>>();

	enemyNeighbours = this.getEnemyNeighbours(m, player);

	for (Pair<Byte, Byte> enemy : enemyNeighbours) {
	    enemyGroups.add(this.getStoneGroup(enemy, player));
	}
	for (List<Pair<Byte, Byte>> enemyGroup : enemyGroups) {
	    if (this.isDestroyed(enemyGroup)) {
		enemiesDestroyed.addAll(enemyGroup);
	    }
	}
	for (Pair<Byte, Byte> destroyedStone : enemiesDestroyed) {
	    this.destroy(destroyedStone);
	}
    }

    private void destroy(Pair<Byte, Byte> destroyedStone) {
	this.getB()[destroyedStone.fst][destroyedStone.snd] = this
		.getPlayerNone();
    }

    @Override
    public Go doMove(GoMove gm) {
	final Pair<Byte, Byte> m = gm.getMv();
	Go result = this.clone();
	result.setPlayer(this.nextPlayer());
	if (gm instanceof GoNoMove) {
	    return result;
	}
	result.getB()[m.fst][m.snd] = this.getPlayer();
	if (!result.isDestroyed(result.getStoneGroup(m, this.getPlayer()))) {
	    result.dealWithEnemies(m, this.nextPlayer());
	    result.setMovesDone(this.getMovesDone() + 1);
	} else {
	    result = this;
	}
	// System.out.println("ONE: "+result.evalState(ONE)+", TWO: "+result.evalState(TWO));
	return result;
    }

    public Go doMove(Pair<Byte, Byte> m) {
	Go thisTurn = this.clone();
	if (this.getB()[m.fst][m.snd] == getNone()) {
	    final List<Pair<Byte, Byte>> turn = this.toTurn(m);
	    if (!turn.isEmpty()) {
		thisTurn = this.doMove(new GoMove(m, turn));
	    }
	}

	return thisTurn;
    }

    @Override
    public boolean ended() {
	return this.noMoreMove();
    }

    @Override
    public int evalState(byte player) {
	int result = 0;
	for (byte trs = 0; trs < this.getColumns(); trs++) {
	    for (byte pos = 0; pos < this.getRows(); pos++) {
		if (this.getB()[trs][pos] == player) {
		    result++;
		}
	    }
	}
	;
	return result;
    }

    private List<Pair<Byte, Byte>> getEnemyNeighbours(Pair<Byte, Byte> from,
	    byte player) {
	final List<Pair<Byte, Byte>> result = new LinkedList<Pair<Byte, Byte>>();
	for (byte x = -1; x < 2; x++) {
	    for (byte y = -1; y < 2; y++) {
		if (this.coordsValid(from.fst + x, from.snd + y)
			&& !(x == y || x == 1 && y == -1 || x == -1 && y == 1)
			&& this.getB()[from.fst + x][from.snd + y] == player) {
		    result.add(new Pair<Byte, Byte>((byte) (from.fst + x),
			    (byte) (from.snd + y)));
		}
	    }
	}
	return result;
    }

    private List<Pair<Byte, Byte>> getStoneGroup(Pair<Byte, Byte> enemy,
	    byte ofPlayer) {
	final List<Pair<Byte, Byte>> enemyGroup = new LinkedList<Pair<Byte, Byte>>();
	List<Pair<Byte, Byte>> tmp = new LinkedList<Pair<Byte, Byte>>();
	enemyGroup.add(enemy);
	int previousSize = 0;
	do {
	    tmp = new LinkedList<Pair<Byte, Byte>>();
	    previousSize = enemyGroup.size();
	    for (Pair<Byte, Byte> newEnemy : enemyGroup) {
		tmp.addAll(this.getEnemyNeighbours(newEnemy, ofPlayer));
	    }

	    for (Pair<Byte, Byte> thisEnemy : tmp) {
		if (!enemyGroup.contains(thisEnemy)) {
		    enemyGroup.add(thisEnemy);
		}
	    }
	}

	while (enemyGroup.size() > previousSize);
	return enemyGroup;
    }

    private boolean isDestroyed(List<Pair<Byte, Byte>> enemyGroup) {
	boolean result = true;
	for (Pair<Byte, Byte> enemy : enemyGroup) {
	    for (byte x = -1; x < 2; x++) {
		for (byte y = -1; y < 2; y++) {
		    if (this.coordsValid(enemy.fst + x, enemy.snd + y)
			    && !(x == y || x == 1 && y == -1 || x == -1
				    && y == 1)
			    && this.getB()[enemy.fst + x][enemy.snd + y] == this
				    .getPlayerNone()) {
			result = false;
		    }
		}
	    }
	}
	return result;
    }

    @Override
    public List<GoMove> moves() {
	final List<GoMove> result = new LinkedList<GoMove>();
	if (this.getMovesDone()<2){
	    for (int i=0;i<4;i++) {
		if (this.getB()[startMovesX[i]][startMovesY[i]] == getNone()) {	
		    Pair<Byte, Byte> nextPos = new Pair<Byte,Byte>((byte)startMovesX[i],(byte)startMovesY[i]);
		    final List<Pair<Byte, Byte>> turn = this
				.toTurn(nextPos);
			if (!turn.isEmpty()) {			    
			    GoMove newMove = new GoMove(nextPos, turn);
			    result.add(newMove);
			}	   
		}		
	    }
	}
	
	for (byte trs = 0; trs < this.getColumns(); trs++) {
	    for (byte pos = 0; pos < this.getRows(); pos++) {

		if (this.getB()[trs][pos] == nextPlayer()) {
		    Pair<Byte, Byte> nextPos = null;
		    for (byte x = -1; x < 2; x++) {
			for (byte y = -1; y < 2; y++) {
			    if (this.coordsValid(trs + x, pos + y)
				    && !(x == 0 && y == 0)
				    && (this.getB()[trs + x][pos + y] == getNone())) {
				nextPos = new Pair<Byte, Byte>(
					(byte) (trs + x), (byte) (pos + y));
				final List<Pair<Byte, Byte>> turn = this
					.toTurn(nextPos);
				if (!turn.isEmpty()) {
				    GoMove newMove = new GoMove(nextPos, turn);
				    result.add(newMove);
				}
			    }
			}
		    }
		}
	    }
	}
	if (result.isEmpty()) {
	    result.add(new GoNoMove());
	}
	return result;
    }

    @Override
    public boolean noMoreMove() {
	return this.getMovesDone() == (this.getColumns() * this.getRows())
		|| passed == 2;
    }

    @Override
    public Go setAtPosition(byte column, byte row) {
	return this.doMove(new Pair<Byte, Byte>(column, row));
    }

    @Override
    public String toString() {
	String tmp = "";
	for (int reihe = 0; reihe < this.getColumns(); reihe++) {
	    for (int spalte = 0; spalte < this.getColumns(); spalte++) {
		tmp += "[" + spalte + "," + reihe + "]:"
			+ this.getB()[spalte][reihe] + "  ";
	    }
	    tmp += "\n";
	}
	return tmp;
    }

    public List<Pair<Byte, Byte>> toTurn(Pair<Byte, Byte> m) {
	final List<Pair<Byte, Byte>> result = new LinkedList<Pair<Byte, Byte>>();
	result.add(new Pair<Byte, Byte>(m.fst, m.snd));
	return result;
    }

    @Override
    public boolean wins(byte player) {
	return this.noMoreMove() && this.evalState(player) > 32;
    }

    public boolean pass(GoControl control) {
	boolean answer = true;
	Go result = this.clone();
	result.setPlayer(this.nextPlayer());
	control.getBoard().setGame(result);
	control.setSession(result);
	if (isPassed()) {
	    result.setGameDone();
	    answer = false;
	} else {
	    result.setPassed(true);
	}
	control.refresh();
	return answer;
    }

    public void setPassed(boolean passed) {
	this.passed = passed ? 1 : 0;
    }

    public boolean isPassed() {
	return this.passed == 1;
    }

    public void setGameDone() {
	this.passed = 2;
    }

}
